package uk.ac.cam.djjb2.mygame;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.newdawn.slick.SpriteSheet;

public class TileMap {
	private final class AttributeMapImpl implements AttributeMap, TileMapObserver {
		private final String name;
		private List<AttributeMapObserver> observers = new ArrayList<AttributeMapObserver>();

		private AttributeMapImpl(String name) {
			this.name = name;
		}

		@Override
		public Vector2i getSize() {
			return TileMap.this.getSize();
		}

		@Override
		public boolean get(Vector2i pos) {
			return TileMap.this.getTileAttribute(pos, name);
		}

		@Override
		public void addObserver(AttributeMapObserver observer) {
			if (observers.isEmpty())
				TileMap.this.addObserver(this);
			observers.add(observer);
		}

		@Override
		public void removeObserver(AttributeMapObserver observer) {
			observers.remove(observer);
			if (observers.isEmpty())
				TileMap.this.removeObserver(this);
		}

		@Override
		public void tileChanged(Vector2i pos, int oldTileId, int newTileId) {
			boolean oldValue = TileMap.this.getTile(oldTileId).getAttribute(TileMap.this, pos, name);
			boolean newValue = TileMap.this.getTile(newTileId).getAttribute(TileMap.this, pos, name);
			
			if (newValue != oldValue) {
				for (AttributeMapObserver observer : observers) {
					observer.attributeChanged(pos, name, newValue);
				}
			}
		}
	}
	private int[][] tiles;
	private Vector2i size;
	private SpriteSheet spriteSheet;
	private TileSet tileSet;
	private List<TileMapObserver> observers = new ArrayList<TileMapObserver>();
	private Map<String, AttributeMap> attributeMaps = new HashMap<String, AttributeMap>();
	
	public TileMap(Vector2i size, SpriteSheet spriteSheet, TileSet tileSet) {
		this.spriteSheet = spriteSheet;
		this.tileSet = tileSet;
		clear(size);
	}
	public void clear(Vector2i size) {
		this.size = size;
		this.tiles = new int[size.y][size.x];
	}
	public int getTileImageIndex(Vector2i pos) {
		Tile tile = getTile(pos);
		if (tile != null)
			return tile.getImageIndex(this, pos);
		else
			return -1;
	}
	public boolean getTileAttribute(Vector2i pos, String name) {
		Tile tile = getTile(pos);
		if (tile != null)
			return tile.getAttribute(this, pos, name);
		else
			return false;
	}
	public void render(Vector2i origin, Vector2i offset, Vector2i size) {
		spriteSheet.startUse();
		
		Vector2i tileCount = new Vector2i(
				spriteSheet.getHorizontalCount(),
				spriteSheet.getVerticalCount()
				);
		Vector2i tileSize = new Vector2i(
				spriteSheet.getWidth(),
				spriteSheet.getHeight()
				).div(tileCount);
		
		for (int y = 0; y < size.y; y++) {
			for (int x = 0; x < size.y; x++) {
				Vector2i pos = offset.add(x, y);
				int imageIndex = getTileImageIndex(pos);

				if (imageIndex >= 0) {
					Vector2i p = origin.add(pos.mul(tileSize));
					spriteSheet.renderInUse(p.x, p.y, imageIndex % tileCount.x, imageIndex / tileCount.x);
				}
			}
		}
		
		spriteSheet.endUse();
	}
	public SpriteSheet getSpriteSheet() {
		return spriteSheet;
	}
	public Vector2i getSize() {
		return size;
	}
	public void setTileId(Vector2i pos, int tile) {
		int oldId = tiles[pos.y][pos.x];
		
		if (oldId != tile) {
			tiles[pos.y][pos.x] = tile;
			
			for (TileMapObserver observer : observers)
				observer.tileChanged(pos, oldId, tile);
		}
	}
	public boolean contains(Vector2i pos) {
		return pos.x >= 0 && pos.y >= 0 && pos.x < size.x && pos.y < size.y;
	}
	public int getTileId(Vector2i pos) {
		if (contains(pos))
			return tiles[pos.y][pos.x];
		else
			return 0;
	}
	public Tile getTile(int id) {
		return tileSet.getTile(id);
	}
	public Tile getTile(Vector2i pos) {
		return tileSet.getTile(getTileId(pos));
	}
	public void addObserver(TileMapObserver observer) {
		observers.add(observer);
	}
	public void removeObserver(TileMapObserver observer) {
		observers.remove(observer);
	}
	public AttributeMap getAttributeMap(String name) {
		AttributeMap result = attributeMaps.get(name);
		if (result == null) {
			result = new AttributeMapImpl(name);
			attributeMaps.put(name, result);
		}
		return result;
	}
}
